package Game;
import java.util.*;

/**
 * This class represents the Stores in the game. 
 * 
 * A user has the ability to buy items, sell items, seal items, and work for items
 * at the stores. Each store has a randomly generated set and qty of items. Each time
 * a player visits the same store, the same number and qty of times should be returned
 * to the user. 
 * 
 * @author Ryan Goodrich
 * @version 1.0
 */
public class Store {
	
	private Inventory storeInventory;
	private Caravan playerCaravan;
	
	/**
	 * The store is created by passing it the players caravan class. This class will contain
	 * the players current location, inventory, max weight limit, and available cash
	 * 
	 * @param The players caravan object
	 */
	public Store(Caravan p_caravan) {
		playerCaravan = p_caravan;
		storeInventory = new Inventory();
		generateRandomInventory();
	}
	
	/**
	 * Create random inventory for store
	 * 
	 * Use hash tag and random number generator to create qty of items in store
	 */
	private void generateRandomInventory() {
		
		// check if location has a store
		if (!GameDriver.getLocationList().hasStore(playerCaravan.getCaravanGPS().getCurrentLocation()))
			return;
		
		Random generator = new Random(Integer.parseInt(GameDriver.getLocationList().getRawDataAt(playerCaravan.getCaravanGPS().getCurrentLocation()).get("store_seed_number")));
		int quantity = 0;
		
		// go through all the possible items and generate a random number for the qty of each item
		for (int itemId = 0; itemId < GameDriver.getItemList().size(); itemId ++)
			if ((quantity = generator.nextInt(20) - 5) > 0)
				storeInventory.addItem(itemId, quantity);
	}
	
	/**
	 * Returns the stores inventory
	 * 
	 * @return Inventory of the store
	 */
	public Inventory getStoreInventory() {
		return storeInventory;
	}
	
	/**
	 * Return the value of an item in the inventory of the store
	 * 
	 * @param itemId The id of the item in question
	 * @return Value of item in stores inventory
	 */
	public int getItemValue(int itemId) {
		
		// get all multipliers
		int price = 1;
		if (GameDriver.getItemList().getRawDataAt(itemId).get("price_multiplier") != null)
			price = Integer.parseInt(GameDriver.getItemList().getRawDataAt(itemId).get("price_multiplier"));
		double distance = playerCaravan.getCaravanGPS().getDistanceTraveled() / 500.0;
		double seclusion = GameDriver.getLocationList().getSeclusionFactor(playerCaravan.getCaravanGPS().getCurrentLocation());
		return (int) Math.round(GameDriver.getItemList().getItemPrice(itemId) * (1 + price * distance * seclusion));
	}
	
	/**
	 * Calculates the minimum/ maximum price of an item
	 * 
	 * @param item The item that is being questioned
	 * @param stroeBuying True if the store is buying the product, false if the user is buying the product
	 * @return The min or max value for an item. Min if the user is buying, and max if the store is buying.
	 */
	private int getItemHaggleValue(int itemId, boolean storeBuying) {
		
		if (storeBuying)
			return (int) Math.round(getItemValue(itemId) * 0.75);
		return (int) Math.round(getItemValue(itemId) * 0.9);
	}
	
	/**
	 * Returns the characters
	 * 
	 * @return Arraylist of the characters
	 */
	public ArrayList getCharacters() {
		return playerCaravan.getCharacters();
	}
	
	/**
	 * Calculates the value to return to the user when trying to haggle
	 * 
	 * @param item The item that is being questioned
	 * @param price The price the user is offering for the product
	 * @param storeBuying True if the store is buying the product, false if the user is buying the product
	 * @return The counter off the store is willing to pay for the product
	 */
	private int getItemHagglePrice(int itemId, int price, boolean storeBuying) {
		
		int buyingPrice = getItemHaggleValue(itemId, storeBuying);
		if (storeBuying)
			return (int) Math.floor(buyingPrice - (price - buyingPrice) / 2.0);
		return (int) Math.ceil(buyingPrice + (buyingPrice - price) / 3.0);
	}
	
	/**
	 * Return the player inventory
	 * 
	 * @return Inventory of player
	 */
	public Inventory getPlayerInventory() {
		return playerCaravan.getPlayerInventory();
	}
	
	/** 
	 * Return remaining weight of caravan
	 * 
	 * @return Weight of all items in inventory
	 */
	public int getCaravanWeight() {
		return playerCaravan.getTotalWeightLimit() - playerCaravan.getPlayerInventory().calculateInventoryWeight();
	}
	
	/**
	 * Returns the character object
	 * The character object contains the persons name, profession, daily value
	 * 
	 * @return Current character
	 */
	public Character getCurrentCharacter() {
		return playerCaravan.getCharacters().get(0);
	}
	
	/**
	 * Returns the ammount of money the caravan has
	 * 
	 * @return The total ammount of money available to spend
	 */
	public int getCaravanMoney() {
		return playerCaravan.getPlayerMoney();
	}

	/**
	 * Returns the number of days that the player has been on journey
	 * 
	 * @return The number of days past
	 */
	public int getDay() {
		return playerCaravan.getDaysPast();
	}

	/**
	 * Allows the user to try and buy and items from the store
	 * The user is allowed to try and haggle for the object and get 
	 * it at a cheaper rate
	 * 
	 * @param item The item that is being purchased
	 * @param qty The qty of items being purchased
	 * @param price The price at which each unit is being purchased for 
	 * @return 
	 * 	0 - The item has been successfully purchased and is now in the players inventory
	 * 	-1 - The item cannot be purchased because of a weight restriction
	 * 	-2 - The item cannot be purchased because the player does not have enough money
	 * 	-3 - The store does not have enough in stock
	 * other - The store is making a counter offer and this value is the new stores offered rate
	 */
	public int buy(int itemId, int qty, int price) {
		if (getCaravanWeight() - GameDriver.getItemList().getItemWeight(itemId) * qty < 0)
			return -1;
		else if (getCaravanMoney() - price * qty < 0)
			return -2;
		else if (storeInventory.getQuantityOf(itemId) < qty)
			return -3;
		else if (getItemHaggleValue(itemId, false) <= price) {
			// take care of buying item
			playerCaravan.setPlayerMoney(playerCaravan.getPlayerMoney() - price * qty);
			playerCaravan.getPlayerInventory().addItem(itemId, qty);
			storeInventory.addItem(itemId, qty * -1);
			return 0;
		}
		else
			return getItemHagglePrice(itemId, price, false);
	}
	
	/**
	 * Allows the user to attempt to sell back an item to the store in order to make money
	 * This will never give the user full value for the item he is trying to sell.
	 * E.g. If the person were to buy an Engine at the starting city and then transport
	 * it to one of the final cities then it would be worth more then when purchased,
	 * (distance multiplier -- the product would be worth more at the current store
	 * then the one it was originally purchased at). However, if the value decreased
	 * for some reason then the player would take a loss.
	 * The value of the item is deteremined by the current location, not by the locaiton
	 * that it was purchased from
	 * 
	 * @param item The item trying to be sold
	 * @param qty The number of items trying to be sold
	 * @param price The price the user is asking for 
	 * @return 
	 * 	0 - The item has been successfully sold and is now in the stores inventory
	 * 	-1 - The player does not actually possess the item
	 * other - The store is making a counter offer and this value is the new stores offered rate
	 */
	public int sell(int itemId, int qty, int price) {
		if (playerCaravan.getPlayerInventory().getQuantityOf(itemId) < qty)
			return -1;
		else if (getItemHaggleValue(itemId, true) >= price) {
			playerCaravan.setPlayerMoney(playerCaravan.getPlayerMoney() + price * qty);
			playerCaravan.getPlayerInventory().addItem(itemId, qty * -1);
			storeInventory.addItem(itemId, qty);
			return 0;
		}
		else
			return getItemHagglePrice(itemId, price, true);
	}
	
	/**
	 * Allow user to try and steal an item from the store
	 * If trying to steal more then one item, then he must
	 * successfully be able to get each item out of the store
	 * 
	 * NOTE: This does not check that a person is not stealing more then his
	 * caravan can transport. Please check the weight of the caravan before the
	 * user leaves the store to make sure that he can still move
	 * 
	 * NOTE: If the player is not successful they will be "shot."
	 * Please check for the next available player before attempting to
	 * buy more items from the store
	 * 
	 * @param item The item that is being stolen
	 * @param qty The qty of items that are being stolen
	 * @return If the player was successful or not in sealing an item
	 * 	If the player was not successful the player is now dead and a new leader will need to be chosen
	 */
	public boolean steal(int itemId, int qty) {
		if (getCurrentCharacter() == null)
			return false;
		int maxValue = getCurrentCharacter().getProfession().getPercentThief();
		boolean success = true;
		Random generator = new Random();
		for (int i = 0; i < qty; i++)
			success = success && (generator.nextInt(100) < maxValue);
		if (!success) {
			getCurrentCharacter().setHealth(0);
			playerCaravan.getCharacters().remove(0);
			return false;
		}
		playerCaravan.getPlayerInventory().addItem(itemId, qty);
		storeInventory.addItem(itemId, qty * -1);
		return true;
	}
//	
	/**
	 * Allows users to wait and work for a certain number of days to buy
	 * an item in the store. The number of days the person will have to wait
	 * is determined by the profession.
	 * 
	 * @param item The item being worked for
	 * @param qty The qty of items being worked for
	 */
	public void work(int itemId, int qty) {
		
		for (int i = 0; i < getDaysOfWork(itemId) * qty; i++)
			playerCaravan.increaseDay();
		playerCaravan.getPlayerInventory().addItem(itemId, qty);
		storeInventory.addItem(itemId, qty * -1);
	}

	/**
	 * Returns the number of days it will take for the entire caravan
	 * to work in order to by one item. The minimum number of days will be 1
	 * 
	 * @param item The item in question
	 * @return The number of days it will take to buy one item
	 */
	public int getDaysOfWork(int itemId) {
		
		int dailyValue = 0;
		for (Character curPerson : playerCaravan.getCharacters())
			dailyValue += curPerson.getProfession().getDailyValue();
		
		return Math.min(1, getItemValue(itemId) / dailyValue);
	}
	
}
